package me.sealer.algorithm.hash;

import me.sealer.algorithm.hash.node.ClientNode;
import me.sealer.algorithm.hash.node.DefaultClientNode;
import me.sealer.algorithm.hash.node.RealServerNode;
import me.sealer.algorithm.hash.node.VirtualServerNode;

import java.util.*;

/**
 * @author Sealer
 * @date 2017/03/13
 */
public class ConsistentHash {
    /**
     * 虚拟节点的数目，这里写死，为了演示需要，一个真实结点对应5个虚拟节点
     */
    private static final int DEFAULT_VIRTUAL_NODES_NUM = 5;
    /**
     * 待添加入Hash环的服务器列表
     */
    private static String[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111",
            "192.168.0.3:111", "192.168.0.4:111"};

    /**
     * 真实结点列表,考虑到服务器上线、下线的场景，即添加、删除的场景会比较频繁，这里使用LinkedList会更好
     */
    private static List<RealServerNode> realServerNodes = new LinkedList<>();

    /**
     * 虚拟节点，key表示虚拟节点的hash值，value表示虚拟节点的名称
     */
    private static SortedMap<Integer, VirtualServerNode> virtualServerNodesMap = new TreeMap<>();

    private static List<ClientNode> clientNodeLinkedList = new LinkedList<>();

    // 静态块代码
    static {
        initRealServerNodes(Arrays.asList(servers));
        initVirtualServerNodes(DEFAULT_VIRTUAL_NODES_NUM);
    }

    /**
     * 初始化最初真实节点
     *
     * @param serverStrList
     */
    private static void initRealServerNodes(List<String> serverStrList) {
        serverStrList.forEach(serverStr -> {
            String[] hostPort = serverStr.split(":");
            if (hostPort.length == 2) {
                String host = hostPort[0];
                String portStr = hostPort[1];
                int port = Integer.parseInt(portStr);
                realServerNodes.add(new RealServerNode(host, port));
            }
        });
    }

    /**
     * 添加虚拟节点
     *
     * @param virtualNodesNum
     */
    private static void initVirtualServerNodes(int virtualNodesNum) {
        realServerNodes.forEach(realServerNode -> addVirtualServerNodes(realServerNode, virtualNodesNum));
    }

    /**
     * 为真实节点添加虚拟节点
     *
     * @param realServerNode
     * @param virtualNodesNum
     */
    private static void addVirtualServerNodes(RealServerNode realServerNode, int virtualNodesNum) {
        for (int i = 0; i < virtualNodesNum; i++) {
            VirtualServerNode virtualServerNode = new VirtualServerNode(i, realServerNode);
            int hash = getHash(virtualServerNode.genHashKey());
            virtualServerNodesMap.put(hash, virtualServerNode);
        }
    }

    /**
     * 处理刚加入的客户端节点
     * @param clientNode
     */
    public static void processClientNode(ClientNode clientNode) {
        RealServerNode realServerNode = mapClientNode2ServerNode(clientNode);
        // 加入客户端节点列表
        clientNode.setRealServerNode(realServerNode);
        clientNodeLinkedList.add(clientNode);
    }

    /**
     * 服务端节点增加或删除时触发， 重新映射客户端节点对应的服务端节点
     */
    private static void remappingClientNodes() {
        clientNodeLinkedList.forEach(clientNode -> {
            RealServerNode realServerNode = mapClientNode2ServerNode(clientNode);
            if (realServerNode != clientNode.getRealServerNode()) {
                clientNode.setRealServerNode(realServerNode);
            }
        });
    }

    /**
     * 使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法，最终效果没区别
     */
    private static int getHash(String str) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < str.length(); i++) {
            hash = (hash ^ str.charAt(i)) * p;
        }
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;

        // 如果算出来的值为负数则取其绝对值
        if (hash < 0) {
            hash = Math.abs(hash);
        }
        return hash;
    }

    /**
     * 得到应当路由到的真实结点
     *
     * @param clientNode
     * @return
     */
    private static RealServerNode mapClientNode2ServerNode(ClientNode clientNode) {
        // 得到带路由的结点的Hash值
        int hash = getHash(clientNode.genHashKey());
        // 得到大于该Hash值的所有Map
        SortedMap<Integer, VirtualServerNode> subMap = virtualServerNodesMap.tailMap(hash);
        // 第一个Key就是顺时针过去离node最近的那个结点
        Integer i = subMap.firstKey();
        // 返回对应的虚拟节点名称，这里字符串稍微截取一下
        VirtualServerNode virtualServerNode = subMap.get(i);
        return virtualServerNode.getRealServerNode();
    }

    /**
     * 添加一个真实节点
     *
     * @param host
     * @param port
     */
    public static void addRealServerNode(String host, int port, int virtualNodesNum) {
        RealServerNode realServerNode = new RealServerNode(host, port);
        realServerNodes.add(realServerNode);
        addVirtualServerNodes(realServerNode, virtualNodesNum);
        remappingClientNodes();
    }

    public static void deleteRealServerNode(String host, int port) {
        Iterator<RealServerNode> iterator = realServerNodes.iterator();
        while (iterator.hasNext()) {
            RealServerNode realServerNode = iterator.next();
            if (realServerNode.getHost().equals(host) && realServerNode.getPort() == port) {
                Iterator<Map.Entry<Integer, VirtualServerNode>> iter = virtualServerNodesMap.entrySet().iterator();
                while (iter.hasNext()) {
                    Map.Entry<Integer, VirtualServerNode> entry = iter.next();
                    if (entry.getValue().getRealServerNode() == realServerNode) {
                        iter.remove();
                    }
                }
                iterator.remove();
            }
        }
        remappingClientNodes();
    }

    public static void main(String[] args) {
        List<ClientNode> clientNodes = new ArrayList<>(4);
        clientNodes.add(new DefaultClientNode("127.0.0.1", 6666));
        clientNodes.add(new DefaultClientNode("221.226.0.8", 2222));
        clientNodes.add(new DefaultClientNode("10.211.15.1", 3333));
        clientNodes.forEach(clientNode -> {
            processClientNode(clientNode);
            System.out.println("[" + clientNode + "]的hash值为" +
                    getHash(clientNode.genHashKey()) + ", 被路由到结点[" + clientNode.getRealServerNode() + "]");

        });
        clientNodeLinkedList.forEach(clientNode -> System.out.println(clientNode.getRealServerNode()));

        deleteRealServerNode("192.168.0.3", 111);
        clientNodeLinkedList.forEach(clientNode -> System.out.println(clientNode.getRealServerNode()));
    }
}